import importlib
import xml.dom.minidom
import codecs
import os
import os.path
import runpy
import sys
from math import radians, sin, cos, atan2, sqrt

global trace_stream
trace_stream = open("/dev/null","w")

def set_trace_stream(s) :
    global trace_stream;
    trace_stream = s

import td_encrypt
from td_geocode_cloudmade import getCoordinatesIfMissing

FLAGS_GEN_TQZ=1
FLAGS_GEN_GPX=2
FLAGS_GEN_PY=4
FLAGS_GEN_EVERYTHING=255

def great_circle_distance_metres_and_bearing_degrees(lat1, long1, lat2, long2) :
    # convert all coordinates to radians
    lat1, long1, lat2, long2 = map(radians, [lat1, long1, lat2, long2])
    dlong = long2 - long1
    dlat = lat2 - lat1
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlong/2)**2
    c = 2 * atan2(sqrt(a), sqrt(1-a))
    return (6371000.0 * c,  nil)

def process_dir(indir,outdir,flags) :
    # Attempt to create the output directory
    # tolerate it silently if the directory already
    # exists
    try : os.mkdir(outdir)
    except OSError : pass

    for f in sorted(os.listdir(indir)) :
        if(f.endswith('.py')) :
            name = f.replace('.py','')
            process_file(name,indir,outdir,flags)

def process_file(name,indir,outdir,flags) :
    TrailDataImporter(indir,name,outdir,flags).gen_files()

class TrailDataImporter :
    def __init__(self,indir,name,outdir,flags) :
        self.indir = indir
        self.name = name
        self.outdir = outdir
        self.flags = flags
        dict = runpy.run_path(os.path.join(indir,name)+".py")
        # print repr(dict)
        self.trail = dict['trail']
        self.geocodeForMissingCoordinates()
    def geocodeCollectionIfPresent(self,owner,cname) :
        try :
            for c_item in owner[cname] :
                (c_item["latitude"],c_item["longitude"],trace_msg) = \
                    getCoordinatesIfMissing(self.trail["locality"],c_item)
                trace_stream.write(trace_msg+"\n")
                self.geocodeCollectionIfPresent(c_item,"points")
                self.geocodeCollectionIfPresent(c_item,"zones")
        except KeyError:
            pass
    def geocodeForMissingCoordinates(self) :
        locality = self.trail["locality"]
        trace_stream.write("Geocoding for missing coordinates in trail %s\n"%(locality,))
        self.geocodeCollectionIfPresent(self.trail,"points")
        self.geocodeCollectionIfPresent(self.trail,"zones")
    def toPy(self) :
        INDENT=u"    "
        TRAIL_TEMPLATE = u"trail = {\n%s\n}\n"
        SCALAR_ATTR_TEMPLATE = u"""'%s': u"%s", """
        LIST_ATTR_TEMPLATE = INDENT + SCALAR_ATTR_TEMPLATE + u"\n"
        ZONE_ATTR_TEMPLATE = INDENT + INDENT + INDENT + u"""'%s': u"%s",\n"""
        body = u""
        scalar_attrs_string = u"";
        for aname in ( "locality","version","mode","description") :
            try : scalar_attrs_string += INDENT + SCALAR_ATTR_TEMPLATE%(aname,self.trail[aname].encode('utf-8')) + u"\n"
            except KeyError : pass
        body += scalar_attrs_string
        for aname in ( "zones","points") :
            try :
                list_attrs_string = INDENT + "'" + aname + "': (\n"
                #"""
                for z in self.trail[aname] :
                    list_attrs_string += INDENT + INDENT + u"{\n"
                    for aname2 in ( 'name', 'address','message','url','latitude','longitude','radius_metres') :
                        #try: list_attrs_string += ZONE_ATTR_TEMPLATE%(aname2,z[aname2].decode('utf-8').encode('utf-8'))
                        try: list_attrs_string += ZONE_ATTR_TEMPLATE%(aname2,z[aname2])
                        except KeyError: pass
                        except UnicodeDecodeError:pass
                    list_attrs_string += INDENT + INDENT + u"},\n"
                #"""
                list_attrs_string += INDENT+"),\n"
                body += list_attrs_string
            except KeyError : pass
        f = codecs.open(os.path.join(self.outdir,self.name+".py"),"w",encoding='utf-8',errors='strict')
        f.write(u"# td_%s.py\n# -*- coding:utf-8 -*- \n\n"%(self.name,))
        f.write(TRAIL_TEMPLATE%(body,))
    def createZoneNode(self,doc,z) :
        zoneNode = doc.createElement("zone")
        for attr_name in ( "name", "address", "message", "url",) :
            try : zoneNode.attributes[attr_name]=z[attr_name]
            except KeyError: pass
            except :
                print list(z[attr_name])
                pass
        coordNode = doc.createElement("coordinates")
        for attr_name in ( "latitude", "longitude","radius_metres") :
            try : coordNode.attributes[attr_name]=str(z[attr_name])
            except : pass
        zoneNode.appendChild(coordNode)
        for attr_name in ( "points", "zones" ) :
            try :
                for nestedZone in z[attr_name] :
                    zoneNode.appendChild(self.createZoneNode(doc,nestedZone))
            except: pass
        return zoneNode
    def addEncryptedNodeToTqz(self,attachmentNode,encryptedNodeName,plaintext) :
        pt = plaintext.encode('utf-8')
        # print pt
        ptlen = len(pt)
        ptcrc = '%08x' % (binascii.crc32(pt) & 0xffffffff)
        ct = ''
        while(len(pt)>0) :
            pt_block = pt[0:127]
            pt = pt[128:]
            ct += str(encrypting_key.encrypt(pt_block,""))
        ctlen = len(ct)
        ctcrc = '%08x' % (binascii.crc32(ct) & 0xffffffff)
        ct_b64 = ct.encode("base64")
    def toTqz(self) :
        doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "tw1", None)
        topNode = doc.documentElement
        clearNode = doc.createElement("tw1_plaintext")
        for k in ( "version", "locality", "description", "mode") :
            try : clearNode.attributes[k]=self.trail[k]
            except KeyError : pass
        try :
            for z in self.trail["zones"] :
                zoneNode = self.createZoneNode(doc,z)
                clearNode.appendChild(zoneNode)
        except KeyError: pass
        topNode.appendChild(clearNode)
        xml_text = topNode.toprettyxml(" ","\n","utf-8")
        (ct_b64,ptlen,ptcrc,ctlen,ctcrc) = td_encrypt.encrypt(xml_text)
        encryptedNode = doc.createElement("tw1_encrypted")
        b64_cryptext_child = doc.createTextNode("\n%s"%ct_b64)
        encryptedNode.appendChild(b64_cryptext_child)
        encryptedNode.attributes["ptlen"] = str(ptlen)
        encryptedNode.attributes["ptcrc"] = str(ptcrc)
        encryptedNode.attributes["ctlen"] = str(ctlen)
        encryptedNode.attributes["ctcrc"] = str(ctcrc)
        topNode.appendChild(encryptedNode)
        f = open(os.path.join(self.outdir,self.name+".tqz"),"w")
        f.write(doc.toprettyxml("    ","\n",'utf-8'))
        f.close()
    def addWaypointToGpx(self,doc,gpxNode,pos) :
        wptNode = doc.createElement("wpt")
        wptNode.attributes["lat"]=str(pos[0])
        wptNode.attributes["lon"]=str(pos[1])
        gpxNode.appendChild(wptNode)
    def addSectorToGpx(self,doc,gpxNode,pos,next_pos,metres_per_waypoint) :
        (sector_length_in_metres,_) = great_circle_distance_metres_and_bearing_degrees(
            pos[0],pos[1],next_pos[0],next_pos[1]
        );
        print "Sector length = %7.4f m"%sector_length_in_metres
        intermediate_point_count = int(sector_length_in_metres/metres_per_waypoint);
        if(intermediate_point_count>0) :
            delta_lat = (next_pos[0] - pos[0]) / intermediate_point_count
            delta_long = (next_pos[1] - pos[1]) / intermediate_point_count
            for i in range(1,intermediate_point_count) :
                pos = (pos[0]+delta_lat,pos[1]+delta_long)
                self.addWaypointToGpx(doc,gpxNode,pos)
    def toGpx(self) :
        intermediate_point_count=10
        doc = xml.dom.minidom.getDOMImplementation().createDocument(None, "gpx", None)
        gpxNode = doc.documentElement
        gpxNode.attributes["version"]="1.1"
        gpxNode.attributes["creator"]="http://code.google.com/p/tl-trailer-tqz-creation-scripts"
        zone_centre_list = []
        for z in self.trail["zones"] :
            zone_centre_list += [ z ]
            try : zone_centre_list += z["zones"]
            except KeyError : pass
        zone_centre_list += self.trail["zones"][0]
        if(len(zone_centre_list)>0) :
            pos = (float(zone_centre_list[0]["latitude"]),float(zone_centre_list[0]["longitude"]))
            self.addWaypointToGpx(doc,gpxNode,pos)
            for p in zone_centre_list[1:] :
                try :
                    next_pos = (float(p["latitude"]),float(p["longitude"]))
                    self.addSectorToGpx(doc,gpxNode,pos,next_pos,25.0)
                    self.addWaypointToGpx(doc,gpxNode,next_pos)
                except : pass
                pos = next_pos
            # finally close the loop
            next_pos = (float(zone_centre_list[0]["latitude"]),float(zone_centre_list[0]["longitude"]))
            self.addSectorToGpx(doc,gpxNode,pos,next_pos,10.0)
        f = open(os.path.join(self.outdir,self.name+".gpx"),"w")
        f.write(doc.toprettyxml("    ","\n",'utf-8'))
        f.close()
    def gen_files(self) :
        if(self.flags|FLAGS_GEN_TQZ) : self.toTqz()
        if(self.flags|FLAGS_GEN_GPX) : self.toGpx()
        if(self.flags|FLAGS_GEN_PY) : self.toPy()

def td_gen(name) :
    TrailDataImporter(name).gen_files()

